//TODO: AdaptiveThresholding, check if result is ok

using System;
using System.Diagnostics;
using System.Collections;
using System.Collections.Generic;
using System.Drawing;
using System.Drawing.Imaging;
using System.Windows.Forms;

using LowLevelGraphics.Histograms;

namespace LowLevelGraphics.Filter
{
    /// <summary>
    /// 
    /// </summary>
    public enum THRESHOLDING_MODE
    {
        /// <summary>
        /// Use median
        /// </summary>
        MEDIAN,

        /// <summary>
        /// Use mean
        /// </summary>        
        MEAN,

        /// <summary>
        /// Use min/max
        /// </summary>
        MIN_MAX,

        /// <summary>
        /// Use integral Image
        /// </summary>
        INTEGRAL_IMAGE,

        /// <summary>
        /// Use convergence, i.e. use automatic thresholding then use threshold
        /// 
        /// </summary>
        CONVERGENCE,

        /// <summary>
        /// Exactly like CONVERGENCE, BUT only once determines new threshold
        /// </summary>
        AUTOTHRESHOLD_ONCE,

        /// <summary>
        /// Own derived method
        /// </summary>
        OWN_METHOD,

        /// <summary>
        /// Another own derived method
        /// </summary>
        OWN_METHOD2,

        /// <summary>
        /// If same threshold appeared twice
        /// </summary>
        SAME_THRESHOLD_TWICE,

        /// <summary>
        /// Use balanced histogram method
        /// </summary>
        BALANCED_HISTOGRAM
    }

    /// <summary>
    /// AdaptiveThreshold is an algorithm to apply adaptive thresholding to an image.
    /// Choose between min/max, median or mean
    /// </summary>
    public class AdaptiveThresholding : Threshold
    {
        //the width and height of this image in pixels
        protected int m_nWidth = 0, m_nHeight = 0;
        protected Ranking m_Ranking = new Ranking();

        /// <summary>
        /// 
        /// </summary>
        protected THRESHOLDING_MODE M_MODE = THRESHOLDING_MODE.MEAN;
        /// <summary>
        /// 
        /// </summary>
        protected int m_nWindowSize = 3;
        protected int m_nConstant = 7;
        protected bool m_bUseRankingAfterwards = false;

        /// <summary>
        /// Default constructor using min/max mode with a window size of 7
        /// </summary>
        public AdaptiveThresholding()
        {
        }

        /// <summary>
        /// Specify thresholding mode directly
        /// </summary>
        /// <param name="_MODE"></param>
        public AdaptiveThresholding(THRESHOLDING_MODE _MODE)
            : this(_MODE, 8, 7)
        {
        }

        /// <summary>
        /// Specify thresholding mode, window size and constant directly
        /// </summary>
        /// <param name="_MODE"></param>
        /// <param name="_nWindowSize"></param>
        /// <param name="_nConstant"></param>
        public AdaptiveThresholding(THRESHOLDING_MODE _MODE, int _nWindowSize, int _nConstant)
        {
            M_MODE = _MODE;
            m_nWindowSize = _nWindowSize;
            m_nConstant = _nConstant;
        }

        /// <summary>
        /// Use this to create a copy
        /// </summary>
        /// <param name="_adaptiveThresholding"></param>
        internal AdaptiveThresholding(AdaptiveThresholding _adaptiveThresholding)
        {
            this.M_MODE = _adaptiveThresholding.M_MODE;
            this.m_nWindowSize = _adaptiveThresholding.m_nWindowSize;
            this.m_nConstant = _adaptiveThresholding.m_nConstant;
            this.m_bUseRankingAfterwards = _adaptiveThresholding.m_bUseRankingAfterwards;
        }

        /// <summary>
        /// thresholding mode
        /// </summary>
        public THRESHOLDING_MODE ThresholdingMode
        {
            get { return M_MODE; }
            set { M_MODE = value; }
        }

        /// <summary>
        /// kernel filter size
        /// </summary>
        public int WindowSize
        {
            get { return m_nWindowSize; }
            set { m_nWindowSize = value; }
        }

        /// <summary>
        /// Set or read the constant
        /// </summary>
        public int Constant
        {
            get { return m_nConstant; }
            set { m_nConstant = value; }
        }

        /// <summary>
        /// Set or read if a ranking filter will be used afterwards
        /// </summary>
        public bool UseRankingAfterwards
        {
            get { return m_bUseRankingAfterwards; }
            set { m_bUseRankingAfterwards = value; }
        }

        /// <summary>
        /// Executes this filter
        /// </summary>
        /// <param name="_bitmap"></param>
        /// <returns></returns>
        public override UnsafeBitmap Execute(UnsafeBitmap _bitmap)
        {
            int nWidth = _bitmap.Width;
            int nHeight = _bitmap.Height;

            int nThresholdStart = -1;

            int y = 0;
            int x = 0;

            switch (M_MODE)
            {
                case THRESHOLDING_MODE.MEAN:
                    MeanThreshold(_bitmap, nWidth, nHeight, m_nWindowSize, m_nConstant);
                    break;

                case THRESHOLDING_MODE.MEDIAN:
                    MedianThreshold(_bitmap, nWidth, nHeight, m_nWindowSize, m_nConstant);
                    break;

                case THRESHOLDING_MODE.MIN_MAX:
                    MeanMaximumMinimumThreshold(_bitmap, nWidth, nHeight, m_nWindowSize, m_nConstant);
                    break;

                case THRESHOLDING_MODE.INTEGRAL_IMAGE:
                    IntegralImageThreshold(_bitmap, nWidth, nHeight, m_nWindowSize, m_nConstant);
                    break;

                case THRESHOLDING_MODE.CONVERGENCE:
                    nThresholdStart = new Random(DateTime.Now.Millisecond).Next(0, 255);
                    while (nThresholdStart != GetThreshold(_bitmap))
                    {
                        nThresholdStart = GetThreshold(_bitmap);
                        Debug.WriteLine(nThresholdStart);
                    }
                    new Threshold2().Execute(_bitmap);
                    break;

                case THRESHOLDING_MODE.AUTOTHRESHOLD_ONCE:
                    nThresholdStart = new Random(DateTime.Now.Millisecond).Next(0, 255);
                    nThresholdStart = GetThreshold(_bitmap);
                    new Threshold2().Execute(_bitmap);
                    break;

                case THRESHOLDING_MODE.SAME_THRESHOLD_TWICE:
                    nThresholdStart = new Random(DateTime.Now.Millisecond).Next(0, 255);
                    SortedList<int, int> aFound = new SortedList<int, int>();
                    while (nThresholdStart != GetThreshold(_bitmap))
                    {
                        nThresholdStart = GetThreshold(_bitmap);
                        if (!aFound.ContainsKey(nThresholdStart))
                        {
                            aFound.Add(nThresholdStart, 0);
                        }
                        else
                        {
                            aFound[nThresholdStart]++;
                            break;
                        }
                        Debug.WriteLine(nThresholdStart);
                    }
                    new Threshold2().Execute(_bitmap);
                    break;

                case THRESHOLDING_MODE.OWN_METHOD:
                    int nThreshold = OwnMethod(_bitmap, nWidth, nHeight, ref y, ref x);
                    break;

                case THRESHOLDING_MODE.OWN_METHOD2:
                    //uses line crossing
                    OwnMethod2(_bitmap, nWidth, nHeight, m_nConstant);
                    break;

                case THRESHOLDING_MODE.BALANCED_HISTOGRAM:
                    nThreshold = BalancedHistogram(_bitmap);
                    new PerPixelFilter(
                        new PerPixelFilter.PerPixelFilterDelegate(delegate(Color _color)
                        {
                            return ColorFunctions.CalculateGrayValue(_color) > nThreshold
                                ? m_ColorForeground : m_ColorBackground;
                        }
                    )).Execute(_bitmap);
                    break;
            }

            if (m_bUseRankingAfterwards)
            {
                m_Ranking.Execute(_bitmap);
            }

            return _bitmap;
        }

        /// <summary>
        /// Own Method. nValue / M-NWindowSize > nThreshold - m_nConstant
        /// </summary>
        /// <param name="_bitmap"></param>
        /// <param name="nWidth"></param>
        /// <param name="nHeight"></param>
        /// <param name="y"></param>
        /// <param name="x"></param>
        /// <returns></returns>
        private int OwnMethod(UnsafeBitmap _bitmap, int nWidth, int nHeight, ref int y, ref int x)
        {
            int nThreshold = GetThreshold(_bitmap);
            for (y = 0; y < nHeight; y++)
            {
                for (x = m_nWindowSize; x < nWidth - m_nWindowSize; x++)
                {
                    int nValue = 0;
                    for (int i = 0; i < m_nWindowSize; i++)
                    {
                        nValue += _bitmap.GetGrayPixelValue(x + i, y);
                    }
                    _bitmap.SetPixel(x, y, (nValue / m_nWindowSize) > nThreshold - m_nConstant ? m_ColorForeground : m_ColorBackground);
                }
            }
            return nThreshold;
        }

        /// <summary>
        /// OwnMethod2 - this uses a "line crossing" approach. 
        /// </summary>
        /// <param name="_bitmap"></param>
        /// <param name="nWidth"></param>
        /// <param name="nHeight"></param>
        /// <returns></returns>
        private void OwnMethod2(UnsafeBitmap _bitmap, int nWidth, int nHeight, int _nConstant)
        {
            ColorCalculation colorCalculation = Definitions.ColorCalculationDictionaryProcessingMode[M_PROCESSING_MODE];

            int[] aWidth = new int[nWidth];
            int nGrayValue = 0;

            int nTempThreshold = 0;
            int[] aHeightThreshold = new int[nWidth];
            int[] aWidthThreshold = new int[nHeight];

            int x = 0;
            int y = 0;
            for (x = 0; x < nWidth; x++)
            {
                nTempThreshold = 0;
                for (y = 0; y < nHeight; y++)
                {
                    nGrayValue = _bitmap.GetGrayPixelValue(x, y);
                    nTempThreshold += nGrayValue;
                }
                nTempThreshold /= nHeight;
                aHeightThreshold[x] = nGrayValue;
            }

            int nLineCrossingThreshold = 0;
            for (y = 0; y < nHeight; y++)
            {
                nTempThreshold = 0;
                for (x = 0; x < nWidth; x++)
                {
                    nGrayValue = _bitmap.GetGrayPixelValue(x, y);
                    nTempThreshold += nGrayValue;
                    aWidthThreshold[y] = nGrayValue;
                }
                nTempThreshold /= nWidth;
                aWidthThreshold[y] = nGrayValue;

                //to avoid a third x*y loop we integrate this here...
                for (x = 0; x < nWidth; x++)
                {
                    nLineCrossingThreshold = (aWidthThreshold[y] + aHeightThreshold[x]) / 2 * (int)m_fScale - _nConstant;
                    _bitmap.SetPixel(x, y, _bitmap.GetGrayPixelValue(x, y) > nLineCrossingThreshold ? m_ColorForeground : m_ColorBackground);
                }
            }
        }

        /// <summary>
        /// 	<param name="_bitmap">pixel array representing image to be thresholded</param>
        /// 	<param name="_nWidth">width of the image in pixels</param>
        /// 	<param name="_nHeight">height of the image in pixels</param>
        /// 	<param name="_nSize">the size of the neigbourhood used in finding the threshold</param>
        /// 	<param name="_nConstant">the constant value subtracted from the mean</param>
        /// </summary>
        /// <param name="_bitmap">The _bitmap.</param>
        /// <param name="_nWidth">Width of the _n.</param>
        /// <param name="_nHeight">Height of the _n.</param>
        /// <param name="_nSize">Size of the _n.</param>
        /// <param name="_nConstant">The _ constant.</param>
        private void MeanThreshold(UnsafeBitmap _bitmap, int _nWidth, int _nHeight, int _nSize, int _nConstant)
        {
            m_nWidth = _nWidth;
            m_nHeight = _nHeight;
            int nMean = 0;
            int nCount = 0;

            UnsafeBitmap bitmap = _bitmap;
            //Now find the sum of values in the size X size neigbourhood 

            Color color = Color.Transparent;

            int y = 0;
            int x = 0;
            int k = 0;
            int l = 0;

            int nSizeHalf = _nSize / 2;
            int nTempX = 0;
            int nTempY = 0;
            for (y = 0; y < m_nHeight; y++)
            {
                for (x = 0; x < m_nWidth; x++)
                {
                    nMean = 0;
                    nCount = 0;
                    //Check the local neighbourhood
                    for (k = 0; k < _nSize; k++)
                    {
                        for (l = 0; l < _nSize; l++)
                        {
                            nTempX = x - nSizeHalf + k;
                            nTempY = y - nSizeHalf + l;
                            if (nTempX > m_nWidth - 1 || nTempX < 0 || nTempY > m_nHeight - 1 || nTempY < 0) continue;
                            color = bitmap.GetPixel(nTempX, nTempY);
                            nMean += ColorFunctions.ToIntRGB(color);
                            nCount++;
                        }
                    }

                    if (nCount == 0) nCount = 1;
                    //Find the mean value
                    nMean = (int)(nMean / nCount) - _nConstant;

                    //Threshold below/above the mean
                    bitmap.SetPixel(x, y, ColorFunctions.ToIntRGB(color)> nMean ? m_ColorForeground : m_ColorBackground);
                }
            }
        }

        /// <summary>
        /// Applies the adaptive thresholding operator to the specified image array
        /// using the median function to find the threshold value
        /// </summary>
        /// <param name="_bitmap">pixel array representing image to be thresholded</param>
        /// <param name="_nWidth">width of the image in pixels</param>
        /// <param name="_nHeight">height of the image in pixels</param>
        /// <param name="_nSize">the size of the neigbourhood used in finding the threshold</param>
        /// <param name="con">the constant value subtracted from the median</param>
        /// <returns></returns>
        public void MedianThreshold(UnsafeBitmap _bitmap, int _nWidth, int _nHeight, int _nSize,
                        int con)
        {
            m_nWidth = _nWidth;
            m_nHeight = _nHeight;
            int nMedian = 0;
            int[] aValue = new int[_nSize * _nSize];
            int nCount = 0;

            UnsafeBitmap bitmap = _bitmap;
            //Now find the values in the size X size neigbourhood 

            int y = 0;
            int x = 0;
            int k = 0;
            int l = 0;
            int nDX = 0;
            int nDY = 0;

            int nHalfSize = _nSize / 2;
            int nSizeSquare = _nSize * _nSize;
            //Now find the values in the size X size neigbourhood 
            for (y = 0; y < m_nHeight; y++)
            {
                for (x = 0; x < m_nWidth; x++)
                {
                    nMedian = 0;
                    nCount = 0;
                    aValue = new int[nSizeSquare];
                    //Check the local neighbourhood
                    //TODO: median threshold, AdaptiveThresholding, speed up
                    for (k = 0; k < _nSize; k++)
                    {
                        for (l = 0; l < _nSize; l++)
                        {
                            nDX = x - nHalfSize + k;
                            nDY = y - nHalfSize + l;
                            if (nDX < 0 || nDY < 0 || nDX > m_nWidth - 1 || nDY > m_nHeight - 1)
                            {
                                continue;
                            }

                            aValue[nCount] = ColorFunctions.ToIntRGB(bitmap.GetPixel(nDX, nDY));
                            nCount++;
                        }
                        //If out of bounds then ignore pixel
                    }
                    //Find the median value

                    //First Sort the array
                    Array.Sort(aValue);

                    //Then select the median
                    nCount /= 2;
                    nMedian = aValue[nCount] - con;

                    //Threshold below/above the median
                    bitmap.SetPixel(x, y, ColorFunctions.ToIntRGB(bitmap.GetPixel(x, y)) >= nMedian ? m_ColorForeground : m_ColorBackground);
                }
            }
        }

        /// <summary>
        /// Balanced Histogram
        /// </summary>
        /// <param name="_bitmap"></param>
        protected virtual int BalancedHistogram(UnsafeBitmap _bitmap)
        {
            Histogram histogram = new Histogram(3, _bitmap);
            
            int i_s = 0;
            int i_e = histogram.Data.Length - 1;
            int i_m = (int)((i_s + i_e) / 2.0f); // center of the weighing scale I_m
            int w_l = GetWeight(i_s, i_m + 1, histogram); // weight on the left W_l
            int w_r = GetWeight(i_m + 1, i_e + 1, histogram); // weight on the right W_r
            while (i_s <= i_e)
            {
                if (w_r > w_l)
                { // right side is heavier
                    w_r -= (int)histogram[i_e--];
                    if (((i_s + i_e) / 2) < i_m)
                    {
                        w_r += (int)histogram[i_m];
                        w_l -= (int)histogram[i_m--];
                    }
                }
                else if (w_l >= w_r)
                { // left side is heavier
                    w_l -= (int)histogram[i_s++];
                    if (((i_s + i_e) / 2) > i_m)
                    {
                        w_l += (int)histogram[i_m + 1];
                        w_r -= (int)histogram[i_m + 1];
                        i_m++;
                    }
                }
            }
            return i_m;
        }

        /// <summary>
        /// Get Weight
        /// </summary>
        /// <returns></returns>
        private int GetWeight(int _nFrom, int _nTo, Histogram _histogram)
        {
            int nWeight = 0;
            for (int i = _nFrom; i < _nTo; i++)
            {
                int nValue = (int)_histogram[i];
                nWeight += (nValue * nValue);
            }
            return nWeight;
        }

        /// <summary>
        /// Applies the adaptive thresholding operator to the specified image array
        /// using the mean of max and min function to find the threshold value
        /// </summary>
        /// <param name="_bitmap">The _bitmap.</param>
        /// <param name="_nWidth">Width of the _n.</param>
        /// <param name="_nHeight">Height of the _n.</param>
        /// <param name="_nSize">Size of the _n.</param>
        /// <param name="_nCon">The _n con.</param>
        public void MeanMaximumMinimumThreshold(UnsafeBitmap _bitmap, int _nWidth, int _nHeight, int _nSize, int _nCon)
        {
            m_nWidth = _nWidth;
            m_nHeight = _nHeight;
            int nMean = 0;
            int nMax = 0, nMin = 0;
            int[] values = new int[_nSize * _nSize];
            int[,] tmp_2d = new int[m_nWidth, m_nHeight];

            int nTemp = 0;

            UnsafeBitmap bitmap = _bitmap;
            //Now find the max and min of values in the size X size neigbourhood 

            int y = 0;
            int x = 0;
            int k = 0;
            int l = 0;
            int nDX = 0;
            int nDY = 0;

            int nCount = 0;
            int nHalfSize = _nSize / 2;
            Color color = Color.Transparent;
            for (y = 0; y < m_nHeight; y++)
            {
                for (x = 0; x < m_nWidth; x++)
                {
                    nMean = 0;
                    color = bitmap.GetPixel(x, y);
                    nMax = ColorFunctions.ToIntRGB(color);
                    nMin = ColorFunctions.ToIntRGB(color);
                    //Check the local neighbourhood
                    for (k = 0; k < _nSize; k++)
                    {
                        for (l = 0; l < _nSize; l++)
                        {
                            nDX = x - nHalfSize + k;
                            nDY = y - nHalfSize + l;

                            if (nDX < 0 || nDY < 0 || nDX > m_nWidth - 1 || nDY > m_nHeight - 1)
                            {
                                nCount++;
                                continue;
                            }

                            //nTemp = ColorFunctions.ToIntRGB(bitmap.GetPixel(nDX, nDY));
                            if (nTemp > nMax)
                            {
                                nMax = nTemp;
                            }
                            if (nTemp < nMin)
                            {
                                nMin = nTemp;
                            }
                        }
                    }

                    //Find the mean value

                    nTemp = nMax + nMin;
                    nTemp /= 2;
                    nMean = nTemp - _nCon;

                    bitmap.SetPixel(x, y, ColorFunctions.ToIntRGB(color) >= nMean ? m_ColorForeground : m_ColorBackground);
                    //Threshold below the mean
                }
            }
        }

        /// <summary>
        /// Uses Integral to calculate Image Threshold
        /// </summary>
        /// <param name="_bitmap"></param>
        /// <param name="_nWidth"></param>
        /// <param name="_nHeight"></param>
        /// <param name="_nWindowSize"></param>
        /// <param name="_nConstant"></param>
        public void IntegralImageThreshold(UnsafeBitmap _bitmap, int _nWidth, int _nHeight, int _nWindowSize, int _nConstant)
        {
            UnsafeBitmap intImg = (UnsafeBitmap)_bitmap.Clone();
            int sum = 0;
            int i = 0;
            int j = 0;
            for (i = 0; i < _nWidth; i++)
            {
                sum = 0;
                for (j = 0; j < _nHeight; j++)
                {
                    sum = sum + ColorFunctions.ToIntRGB(_bitmap[i, j]);
                    if (i == 0)
                    {
                        intImg[i, j] = Color.FromArgb(sum);
                    }
                    else
                    {
                        //intImg[i, j]=  Color.FromArgb(ColorFunctions.ToIntRGB(intImg[i-1, j]) + sum);
                    }
                }
            }

            UnsafeBitmap @out = (UnsafeBitmap)_bitmap.Clone();
            int s = 0;
            sum = 0;
            int x1 = 0;
            int x2 = 0;
            int y1 = 0;
            int y2 = 0;
            int count = 0;
            int t = 0;
            bool bForegroundColor = false;
            for (i = 1; i < _nWidth; i++)
            {
                for (j = 1; j < _nHeight; j++)
                {
                    x1 = i - s / 2; //{border checking is not shown};
                    x2 = i + s / 2;
                    y1 = j - s / 2;
                    y2 = j + s / 2;
                    count = (x2 - x1) * (y2 - y1);
                    sum = ColorFunctions.ToIntRGB(intImg[x2, y2]) - ColorFunctions.ToIntRGB(intImg[x2, y1 - 1]) - ColorFunctions.ToIntRGB(intImg[x1 - 1, y2]) + ColorFunctions.ToIntRGB(intImg[x1 - 1, y1 - 1]);
                    bForegroundColor = (ColorFunctions.ToIntRGB(_bitmap[i, j]) * count) <= (sum * (100 - t) / 100);
                    if (bForegroundColor)
                    {
                        _bitmap[i, j] = m_ColorBackground;
                    }
                    else
                    {
                        _bitmap[i, j] = m_ColorForeground;
                    }
                }
            }
        }

        /// <summary>
        /// Clones this filter
        /// </summary>
        /// <returns></returns>
        public override object Clone()
        {
            return new AdaptiveThresholding(this);
        }
    }
}